﻿using System;
using System.Diagnostics;
using System.Reactive.Linq;
using Microsoft.Xna.Framework;
using SoSlam.Common.Communication;
using SoSlam.Common.Communication.GameMessages;
using SoTools.Communicator;
using System.Collections.Generic;

namespace SoSlam.Server.Console
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var gameLogic = new GameLogic())
            {
                gameLogic.Start();
                System.Console.ReadLine();
                gameLogic.Stop();
            }
        }
    }

    public class GameLogic : IDisposable
    {
        private static Random _random = new Random();

        private readonly Communicator _communicator;

        private IDisposable _playerCommandFlightHandler;
        private IDisposable _playerCommandSlamHandler;
        private IDisposable _playerCommandStartHandler;
        private IDisposable _playerCommandTouchHandler;

        private IDisposable _updateTimer;
        private IDisposable _updateBonusTimer;
        private PlayerPositionMessage _previousPositionMessage;

        DateTime _previousTime;
        DateTime _initialTime;

        private PlayerLogic _player;
        private long _positionCounter;

        private List<BirdState> _birds = new List<BirdState>();
        private float _birdSpawnSpan = 1;
        private float _lastBirdSpawn = 0;
        private int _birdsCounter;
        private int _birdsCasualties;
        private Vector2 _birdsSpeed;

        private List<PowerupState> _powerups = new List<PowerupState>();
        private float _powerupSpawnSpan = 2;
        private float _lastpowerupSpawn = 0;
        private int _powerupsCounter;
        private int _powerupsCatches;
        private bool _powerupsUpdated = false;

        private List<Vector2> _playerInputs = new List<Vector2>();

        private List<Guid> _playerIds = new List<Guid>();
        private GameMode _gameMode = GameMode.OnePlayer;

        private GameStatus _gameStatus;
        private GameStatus GameStatus
        {
            get { return _gameStatus; }
            set
            {
                if (_gameStatus != value)
                {
                    _gameStatus = value;
                    _communicator.Publish(new GameStatusMessage { Status = value });
                    Debug.WriteLine(string.Format("GameStatus : {0}", _gameStatus.ToString()));
                }
            }
        }

        public GameLogic()
        {
            _communicator = new Communicator();
        }

        #region Initialization
        
        public void Start()
        {
            _playerCommandFlightHandler = _communicator.GenerateObservable<PlayerFlightMessage>().Subscribe(HandleFlightMessage);
            _playerCommandSlamHandler = _communicator.GenerateObservable<PlayerSlamMessage>().Subscribe(HandleSlamMessage);
            _playerCommandStartHandler = _communicator.GenerateObservable<PlayerCommandMessage>().Subscribe(HandleCommandMessage);
            _playerCommandTouchHandler = _communicator.GenerateObservable<PlayerTouchMessage>().Subscribe(HandleTouchMessage);

            _communicator.Listen();

            _player = new PlayerLogic(_communicator);
            
            _initialTime = DateTime.Now;
            _previousTime = _initialTime;

            // Game clock
            _updateTimer = Observable.Interval(TimeSpan.FromSeconds(1 / 15f)).Select(i => UpdateGameTime())
                                                                             .Subscribe(Update);

            Observable.Interval(TimeSpan.FromSeconds(0.5))
                .Where(i => GameStatus == GameStatus.Initial)
                .Subscribe(args => _communicator.Publish(ToPlayerPositionMessage(_player.Position, _player.Speed, _player.Power, _positionCounter++)));

            Observable.Interval(TimeSpan.FromSeconds(0.5))
                .Where(i => GameStatus != GameStatus.Running)
                .Subscribe(args =>
                {
                    _communicator.Publish(new PlayerStateMessage { State = _player.PlayerState });
                    _communicator.Publish(new GameStatusMessage { Status = _gameStatus });
                });

            Initialize();
        }

        private GameTime UpdateGameTime()
        {
            var now = DateTime.Now;
            var elapsed = (now - _previousTime).TotalSeconds;
            var total = (now - _initialTime).TotalSeconds;
            _previousTime = now;
            return new GameTime
            {
                TotalGameTime = Convert.ToSingle(total),
                ElapsedGameTime = Convert.ToSingle(elapsed)
            };
        }

        public void Initialize()
        {
            GameStatus = GameStatus.Initial;

            _player.Initialize();

            _birdsSpeed = new Vector2(Constants.BIRD_SPEED_X, 0);

            _playerIds.Clear();
            _powerups.Clear();
            _birds.Clear();

            _positionCounter = 0;
            _powerupsCounter = 0;
            _birdsCounter = 0;

            _powerupsCatches = 0;
            _birdsCasualties = 0;

            _communicator.Publish(ToPlayerPositionMessage(_player.Position, _player.Speed, _player.Power, _positionCounter++));
        }

        #endregion

        #region Update
        
        private void Update(GameTime gameTime)
        {
            if (GameStatus == GameStatus.Running)
            {
                _player.Update(gameTime);

                if (_player.PlayerState == PlayerState.OnTheGround)
                {
                    GameStatus = GameStatus.Ended;
                    _communicator.Publish(new EndOfGameMessage { Score = Convert.ToDecimal(_player.Position.X / 10) });

                    return;
                }

                var positionMessage = ToPlayerPositionMessage(_player.Position, _player.Speed, _player.Power, _positionCounter++);
                if (positionMessage != _previousPositionMessage)
                {
                    _communicator.Publish(positionMessage);
                    _previousPositionMessage = positionMessage;
                }
            }

            UpdateBonus(gameTime);
        }

        private void UpdateBonus(GameTime gameTime)
        {
            if (GameStatus == GameStatus.Running
                && _player.Position.X > 0)
            {
                // Keep spawning new birds & powerups while game is running
                SpawnBirds(gameTime);
                SpawnPowerups(gameTime);
            }

            // Updates the position of instanciated birds
            UpdateBirds(gameTime);
            UpdatePowerUps(gameTime);
        }

        #endregion

        #region Birds

        private void SpawnBirds(GameTime gameTime)
        {
            // Spawns a new bird depending on distance
            if ((gameTime.TotalGameTime - _lastBirdSpawn) > _birdSpawnSpan)
            {
                var coeff = Math.Min((float)Math.Round(_player.Position.X / 1000) / 10f, 0.6);
                _birdSpawnSpan = (float)(_random.NextDouble() * 2 * (1 - coeff));
                _lastBirdSpawn = gameTime.TotalGameTime;

                _birds.Add(new BirdState
                {
                    BirdId = _birdsCounter,
                    X = _player.Position.X + 2000,
                    Y = Math.Max(_player.Position.Y + (300 - 2 * 300 * (float)_random.NextDouble()), 80)
                });

                _birdsCounter++;
            }
        }

        private void UpdateBirds(GameTime gameTime)
        {
            var playerBounds = new Rectangle(
                (int)Math.Round(_player.Position.X),
                (int)Math.Round(_player.Position.Y), 
                Constants.PLAYER_WIDTH_SCALED, 
                Constants.PLAYER_HEIGHT_SCALED);

            // Clean & update positions
            for (int i = 0; i < _birds.Count; i++)
            {
                var bird = _birds[i];

                if (_player.Position.X - bird.X > 500)
                {
                    _birds.Remove(bird);
                }
                else
                {
                    // If bird is hit, stays at the same position
                    if (bird.IsHit)
                        continue;

                    Vector2 birdPosition = new Vector2(bird.X, bird.Y);
                    birdPosition = birdPosition + _birdsSpeed * (gameTime.ElapsedGameTime * Constants.TimeAdjustmentFactor);
                    bird.X = birdPosition.X;
                    bird.Y = birdPosition.Y;

                    var birdBounds = new Rectangle(
                        (int)Math.Round(bird.X), 
                        (int)Math.Round(bird.Y), 
                        Constants.BIRD_WIDTH, 
                        Constants.BIRD_HEIGHT);

                    // Collision test between squirrel and bird
                    if (playerBounds.Intersects(birdBounds))
                    {
                        bird.IsHit = true;
                        _player.Speed = _player.Speed - new Vector2(15, 15);
                    }

                    birdBounds.X -= Constants.TOUCH_MARGIN;
                    birdBounds.Y -= Constants.TOUCH_MARGIN;
                    birdBounds.Width += Constants.TOUCH_MARGIN;
                    birdBounds.Height += Constants.TOUCH_MARGIN;

                    // Collision test between touch action and bird
                    foreach (var playerInput in _playerInputs)
                    {
                        if (birdBounds.Contains((int)Math.Round(playerInput.X), (int)Math.Round(playerInput.Y)))
                        {
                            bird.IsHit = true;
                            _birdsCasualties++;
                            break;
                        }
                    }
                }
            }

            _playerInputs.Clear();

            var birdsMessage = new BirdsMessage();
            birdsMessage.Birds = _birds;
            _communicator.Publish(birdsMessage);
        }

        #endregion

        #region Powerups

        private void SpawnPowerups(GameTime gameTime)
        {
            // Spawns a new powerup depending on distance
            if ((gameTime.TotalGameTime - _lastpowerupSpawn) > _powerupSpawnSpan)
            {
                _powerupSpawnSpan = 1 + _random.Next(1, 3);
                _lastpowerupSpawn = gameTime.TotalGameTime;

                PowerupType type = PowerupType.Gold;
                switch (_random.Next(0, 3))
                {
                    case 0:
                        type = PowerupType.Gold;
                        break;
                    case 1:
                        type = PowerupType.Silver;
                        break;
                    case 2:
                        type = PowerupType.Power;
                        break;
                }

                _powerups.Add(new PowerupState
                {
                    PowerupId = _powerupsCounter,
                    X = _player.Position.X + 2000,
                    Y = Math.Max(_player.Position.Y + 2 * 300 * (float)_random.NextDouble(), 80),
                    Type = type
                });

                _powerupsCounter++;
                _powerupsUpdated = true;
            }
        }
        
        private void UpdatePowerUps(GameTime gameTime)
        {
            var playerBounds = new Rectangle(
                (int)Math.Round(_player.Position.X),
                (int)Math.Round(_player.Position.Y),
                Constants.PLAYER_WIDTH_SCALED,
                Constants.PLAYER_HEIGHT_SCALED);

            // Clean & update positions
            for (int i = 0; i < _powerups.Count; i++)
            {
                var powerup = _powerups[i];

                if (_player.Position.X - powerup.X > 2000)
                {
                    _powerups.Remove(powerup);
                    _powerupsUpdated = true;
                }
                else
                {
                    // If powerup is hit, stays at the same position
                    if (powerup.IsHit)
                        continue;

                    var powerupBounds = new Rectangle(
                        (int)Math.Round(powerup.X),
                        (int)Math.Round(powerup.Y),
                        Constants.POWERUP_WIDTH,
                        Constants.POWERUP_HEIGHT );

                    // Collision test between squirrel and bird
                    if (playerBounds.Intersects(powerupBounds))
                    {
                        powerup.IsHit = true;
                        _powerupsCatches++;

                        switch (powerup.Type)
                        {
                            // Gives speed + height
                            case PowerupType.Gold:
                                _player.Speed += new Vector2(15, 30);
                                break;
                            // Gives speed
                            case PowerupType.Silver:
                                _player.Speed += new Vector2(15, 15);
                                break;
                            // Gives power
                            case PowerupType.Power:
                                _player.Power += 20;
                                _player.Power = MathHelper.Clamp(_player.Power, 0, 100);
                                break;
                        }

                        _powerupsUpdated = true;
                    }
                }
            }

            if (_powerupsUpdated)
            {
                _powerupsUpdated = false;

                var powerupsMessage = new PowerupMessage();
                powerupsMessage.Powerups = _powerups;
                _communicator.Publish(powerupsMessage);
            }
        }

        #endregion

        #region Message received
        
        private void HandleSlamMessage(PlayerSlamMessage message)
        {
            _player.Slam();
        }

        private void HandleFlightMessage(PlayerFlightMessage message)
        {
            _player.Flight(message.Amplitude);
        }

        private void HandleCommandMessage(PlayerCommandMessage message)
        {
            // Starts the game
            if ((GameStatus == GameStatus.Initial || GameStatus == GameStatus.WaitingForPlayer) 
                && message.Command == CommandType.Ready)
            {
                if (_gameMode == GameMode.TwoPlayers)
                {
                    if (!_playerIds.Contains(message.PlayerId))
                    {
                        _playerIds.Add(message.PlayerId);

                        // All players are ready ... here we go !!
                        if (_playerIds.Count == 2)
                        {
                            GetRunning();
                        }
                        else
                        {
                            GameStatus = GameStatus.WaitingForPlayer;
                        }
                    }
                }
                else
                {
                    GetRunning();
                }
            }

            // Reset the game
            if (GameStatus == GameStatus.Ended && message.Command == CommandType.Reset)
            {
                Initialize();
            }
        }

        private void GetRunning()
        {
            GameStatus = GameStatus.Running;

            _player.Fall();
        }

        private void HandleTouchMessage(PlayerTouchMessage msg)
        {
            _playerInputs.Add(new Vector2(msg.X, msg.Y));
        }

        #endregion

        #region Stop & Clean

        public void Stop()
        {
            //TODO Add a stop listening!
        }
        
        public void Dispose()
        {
            _playerCommandFlightHandler.Dispose();
            _playerCommandSlamHandler.Dispose();
            _playerCommandStartHandler.Dispose();
            _playerCommandTouchHandler.Dispose();
            _updateTimer.Dispose();
        }

        #endregion

        #region Misc
        
        public static PlayerPositionMessage ToPlayerPositionMessage(Vector2 position, Vector2 speed, float power, long counterValue)
        {
            return new PlayerPositionMessage
            {
                X = position.X,
                Y = position.Y,
                Sx = speed.X,
                Sy = speed.Y,
                Power = power,
                Order = counterValue,
            };
        }

        #endregion
    }
}
